/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package shaman.storage.impl.filter.published;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import java.awt.Frame;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import shaman.storage.Factory;
import shaman.storage.FilterList;
import shaman.storage.api.Filter;
import shaman.storage.impl.util.IntegerRangeList;

/**
 *
 * @author dmitriy
 */

@XStreamAlias("deflate")
public class DeflateFilter implements Filter {

    public static final String COMPRESSION_LEVEL_PROPERTY="Степень сжатия";
    public static final String FILTER_TYPE = "Deflate";

    private static class DeflateFilterFactory implements Factory<DeflateFilter> {

        public String getTypeName() {
            return FILTER_TYPE;
        }

        public DeflateFilter newObject() {
            return new DeflateFilter();
        }

        public Class<DeflateFilter> getGeneratedClass() {
            return DeflateFilter.class;
        }

    }

    private static class TrackableDeflaterOutputStream extends DeflaterOutputStream {

        private final int streamId;
        private final String openBt;
        
        public TrackableDeflaterOutputStream(OutputStream out, Deflater def) {
            super(out, def);
            StackTraceElement[] bt=Thread.currentThread().getStackTrace();
            openBt=backtraceToString(bt);
            streamId=(new Date().toString()+openBt).hashCode();
            System.err.println("Open DeflaterOutputStream id="+streamId+" at:");
            System.err.println(openBt);
        }

        private static String backtraceToString(StackTraceElement[] bt) {
            StringBuilder btString=new StringBuilder();
            int i;
            for (StackTraceElement el: bt)
                btString.append(el.toString()).append('\n');
            return btString.toString();
        }
        
        @Override
        public void close() throws IOException {
            System.err.println("Closed DeflaterOutputStream id="+streamId+" at:");
            StackTraceElement[] bt=Thread.currentThread().getStackTrace();
            System.err.println(backtraceToString(bt));
            super.close();
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            try {
                super.write(b, off, len);
                System.err.println("Wrote "+len+" bytes to DeflaterOutputStream id="+streamId+" at:");
                StackTraceElement[] bt=Thread.currentThread().getStackTrace();
                System.err.println(backtraceToString(bt));
            } catch (IOException ex) {
                System.err.println("Error writing to DeflaterOutputStream id="+streamId);
                throw ex;
            }
        }


    }

    @XStreamAlias("level")
    private int level=9;

    public DeflateFilter() {
    }

    public FilterType getFilterType() {
        return FilterType.COMPRESSOR;
    }

    public String getName() {
        return FILTER_TYPE;
    }

    public OutputStream getOutputStream(OutputStream compressedStream) {
        return new DeflaterOutputStream(compressedStream, new Deflater(level, true));
    }

    public InputStream getInputStream(InputStream compressedStream) {
        return new InflaterInputStream(compressedStream, new Inflater(true));
    }

    public boolean hasConfigForm() {
        return false;
    }

    public boolean showConfigForm(Frame parent) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<String> getPropertyNames() {
        return Arrays.asList(new String[]{COMPRESSION_LEVEL_PROPERTY});
    }

    public String getPropertyValue(String name) throws InvalidParameterException {
        if (name.equals(COMPRESSION_LEVEL_PROPERTY))
        {
            return String.valueOf(level);
        }
        else {
            throw new InvalidParameterException("Неизвестное имя свойства: "+name);
        }
    }

    public List<String> getPropertyValues(String name) throws InvalidParameterException {
        if (name.equals(COMPRESSION_LEVEL_PROPERTY))
        {
            return new IntegerRangeList(0, 9);
        }
        else {
            throw new InvalidParameterException("Неизвестное имя свойства: "+name);
        }
    }

    public void setPropertyValue(String name, String value) throws InvalidParameterException {
        if (name.equals(COMPRESSION_LEVEL_PROPERTY))
        {
            int newLevel;
            try {
                newLevel=Integer.parseInt(value);
            } catch (NumberFormatException ex) {
                throw new InvalidParameterException("Значение уровня сжатия должно быть числом");
            }
            if (newLevel<0 || newLevel>9)
                throw new InvalidParameterException("Недопустимый уровень сжатия");
            level=newLevel;
        }
        else {
            throw new InvalidParameterException("Неизвестное имя свойства: "+name);
        }
    }

    static {
        System.out.println("Here");
        System.out.flush();
        FilterList.addFactory(new DeflateFilterFactory());
    }

    public static void main(String[] args)
    {
        try {
            Filter deflateFilter=FilterList.getFactoryMap().get("Шифратор").newObject();
            deflateFilter.setPropertyValue(COMPRESSION_LEVEL_PROPERTY, "9");
            ByteArrayOutputStream compressedStream=new ByteArrayOutputStream();
            OutputStream compressStream=deflateFilter.getOutputStream(compressedStream);
            DataOutput compressData=new DataOutputStream(compressStream);
            compressData.writeUTF("Hello World! Hello World! Hello World!");
            compressStream.close();
            compressedStream.close();
            byte[] compressedData=compressedStream.toByteArray();
            StringBuilder dataHex=new StringBuilder("Compressed:");
            int i;
            for (i=0; i<compressedData.length; i++)
            {
                dataHex.append(" ");
                String hex=Integer.toHexString((compressedData[i]+256)%256);
                if (hex.length()<2)
                    dataHex.append("0");
                dataHex.append(hex);
            }
            System.out.println(dataHex.toString());
            InputStream decompressStream=new ByteArrayInputStream(compressedData);
            InputStream decompressedStream=deflateFilter.getInputStream(decompressStream);
            DataInput decompressData=new DataInputStream(decompressedStream);
            String stringRead=decompressData.readUTF();
            decompressedStream.close();
            decompressStream.close();
            System.out.println(stringRead);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
